#include <stdlib.h>
#include <string.h>
#include "imimp4_ffmpeg.h"
#include "imiparser_aac.h"

#ifdef WIN32
#define LOGE(x) #x
#else
#include "log.h"
#endif

extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avio.h>
#include <libavutil/mem.h>
}
#ifdef WIN32
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")
#endif

#define PTS2TIME_SCALE(CurPTS, PrevPTS, timeScale) \
((int64_t)((CurPTS - PrevPTS) * 1000 / (double)(1e+6) * timeScale))

typedef struct _imiffmpegmp4info {
    bool _getfirstframe;
    video_codec_id _video_codec;
    audio_codec_id _audio_codec;
    AVFormatContext *_format_context;
    AVStream *_video_stream;
    unsigned char *_video_extradata;
    unsigned int _video_extradata_size;
    unsigned int _video_frame_index;
    unsigned int _video_fps;
    unsigned int _video_lasttimestamp;
    AVBitStreamFilterContext *_video_bsfc;
    AVStream *_audio_stream;
    unsigned char *_audio_extradata;
    unsigned int _audio_extradata_size;
    unsigned int _audio_frame_index;
} imiffmpegmp4info;

void imimp4_init_ffmpeg() {
    av_register_all();
}

inline int _create_file_inner(imiffmpegmp4info *handle,
                              const char *path,
                              container_format_id container,
                              video_codec_id video,
                              audio_codec_id audio,
                              unsigned int vfps,
                              unsigned int vwidth,
                              unsigned int vheight,
                              unsigned int achannel,
                              unsigned int asamplerate) {
    const char *container_ext = NULL;
    int ret = 0;
    if (handle == 0) return -1;//ASSERT WARNING
    //////////////////////////////////////////////////////////////////////////
    handle->_format_context = avformat_alloc_context();
    if (handle->_format_context == NULL) {
        LOGE("avformat_alloc_context error\n");
        return -1;
    }
    switch (container) {
        case container_format_mp4:
            container_ext = "mp4";
            break;
        default:
            LOGE("container_format_id default\n");
            return -1;
    }
    handle->_format_context->oformat = av_guess_format(container_ext, path, NULL);
    if (handle->_format_context->oformat == NULL) {
        LOGE("av_guess_format error\n");
        goto error;
    }
    switch (video) {
        case video_codec_id_h264:
            handle->_format_context->oformat->video_codec = AV_CODEC_ID_H264;
            break;
        case video_codec_id_h265:
            handle->_format_context->oformat->video_codec = AV_CODEC_ID_H265;
            break;
        default:
            LOGE("video_codec_id default\n");
            goto error;
    }
    switch (audio) {
        case audio_codec_id_g711a:
            handle->_format_context->oformat->audio_codec = AV_CODEC_ID_PCM_ALAW;
            break;
        case audio_codec_id_aac:
            handle->_format_context->oformat->audio_codec = AV_CODEC_ID_AAC;
            break;
        default:
            LOGE("audio_codec_id default\n");
            goto error;
    }
    memcpy(handle->_format_context->filename, path, strlen(path));
    //////////////////////////////////////////////////////////////////////////
    handle->_video_stream = avformat_new_stream(handle->_format_context, NULL);
    if (handle->_video_stream == NULL) {
        LOGE("video_stream avformat_new_stream error\n");
        goto error;
    }
    handle->_video_stream->id = handle->_format_context->nb_streams - 1;
    handle->_video_stream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
    if (handle->_format_context->oformat->flags & AVFMT_GLOBALHEADER)
        handle->_video_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
    switch (video) {
        case video_codec_id_h264:
            handle->_video_stream->codec->codec_id = AV_CODEC_ID_H264;
            handle->_video_stream->codec->level = 0x7F;
            break;
        case video_codec_id_h265:
            handle->_video_stream->codec->codec_id = AV_CODEC_ID_H265;
            break;
        default:
            LOGE("video_codec_id default\n");
            goto error;
    }
    handle->_video_stream->codec->width = vwidth;
    handle->_video_stream->codec->height = vheight;
    handle->_video_stream->codec->time_base = av_d2q(1.0 / vfps, 255);
    handle->_video_stream->r_frame_rate = av_d2q(vfps, 255);
    handle->_video_stream->time_base.num = 1;
    handle->_video_stream->time_base.den = 90000;
    handle->_video_fps = vfps;
    //////////////////////////////////////////////////////////////////////////
    handle->_audio_stream = avformat_new_stream(handle->_format_context, NULL);
    if (handle->_audio_stream == NULL) {
        LOGE("audio_stream avformat_new_stream error");
        goto error;
    }
    handle->_audio_stream->id = handle->_format_context->nb_streams - 1;
    handle->_audio_stream->codec->codec_type = AVMEDIA_TYPE_AUDIO;
    switch (audio) {
        case audio_codec_id_g711a: {
            handle->_audio_stream->codec->codec_id = AV_CODEC_ID_PCM_ALAW;
            handle->_audio_stream->codec->sample_rate = asamplerate;
            handle->_audio_stream->codec->channels = achannel;
        }
            break;
        case audio_codec_id_aac: {
            handle->_audio_stream->codec->codec_id = AV_CODEC_ID_AAC;
            handle->_audio_stream->codec->sample_rate = asamplerate;
            handle->_audio_stream->codec->channels = achannel;
            handle->_audio_stream->codec->profile = FF_PROFILE_AAC_MAIN;
        	handle->_audio_stream->codec->level = 0x01;
            handle->_audio_extradata = imi_make_aac_track_configure(
                    handle->_audio_stream->codec->level,
                    imi_make_aac_header_sampling_frequency_index(
					handle->_audio_stream->codec->sample_rate),
                    handle->_audio_stream->codec->channels);
            handle->_audio_extradata_size = 2;
            handle->_audio_stream->codec->extradata = handle->_audio_extradata;
            handle->_audio_stream->codec->extradata_size = handle->_audio_extradata_size;
            handle->_audio_stream->codec->frame_size = (int)((double)1024*(double)(handle->_audio_stream->codec->sample_rate/(double)16000));
        }
            break;
        default:
            LOGE("audio_codec_id default\n");
            goto error;
    }
    handle->_audio_stream->codec->time_base.num = 1;
    handle->_audio_stream->codec->time_base.den = asamplerate;
    handle->_audio_stream->time_base.num = 1;
    handle->_audio_stream->time_base.den = asamplerate;
    //////////////////////////////////////////////////////////////////////////
    av_dump_format(handle->_format_context, 0, path, 1);
    ret = avio_open(&handle->_format_context->pb, path, AVIO_FLAG_WRITE);
    if (ret < 0) {
        LOGE("avio_open error\n");
        goto error;
    }
    //////////////////////////////////////////////////////////////////////////
    handle->_video_codec = video;
    handle->_video_extradata_size = 0;
    handle->_video_extradata = (unsigned char *) malloc(2048);
    memset(handle->_video_extradata, 0, 2048);
    handle->_audio_codec = audio;
    return 0;

    error:
    if (handle->_video_stream) {
        avcodec_close(handle->_video_stream->codec);
        handle->_video_stream->codec->extradata = NULL;
        handle->_video_stream->codec->extradata_size = 0;
        handle->_video_stream = NULL;
    }
    if (handle->_audio_stream) {
        avcodec_close(handle->_audio_stream->codec);
        handle->_audio_stream->codec->extradata = NULL;
        handle->_audio_stream->codec->extradata_size = 0;
        handle->_audio_stream = NULL;
    }
    avio_close(handle->_format_context->pb);
    handle->_format_context->pb = NULL;
    avformat_free_context(handle->_format_context);
    handle->_format_context = NULL;
    if (handle->_audio_extradata) {
        free(handle->_audio_extradata);
        handle->_audio_extradata = NULL;
    }
    return -1;
}

int imimp4_create_file(const char *path,
                       container_format_id container,
                       video_codec_id video,
                       audio_codec_id audio,
                       unsigned int vfps,
                       unsigned int vwidth,
                       unsigned int vheight,
                       unsigned int achannel,
                       unsigned int asamplerate,
					   /*out*/void **handle) {
    int ret = 0;
    imiffmpegmp4info *handle_impl = (imiffmpegmp4info *) malloc(sizeof(imiffmpegmp4info));
    if (handle_impl == NULL) {
        return -1;
    }
    memset(handle_impl, 0, sizeof(imiffmpegmp4info));
    ret = _create_file_inner(handle_impl, path, container, video, audio, vfps, vwidth, vheight,
                             achannel, asamplerate);
    if (ret != 0) {
        free(handle_impl);
        *handle = NULL;
        return ret;
    }
    *handle = (void *) handle_impl;
    return ret;
}

inline bool
_get_extradata_h264(unsigned char *data, unsigned int data_len, unsigned char *extradata,
                    unsigned int *extradata_size) {
    unsigned char *data_buff = data;
    unsigned char sps_nalu[1024] = {0};
    unsigned int sps_len = 0;
    unsigned char pps_nalu[1024] = {0};
    unsigned int pps_len = 0;
    bool issync = 0;
    while (true) {
        if ((*(unsigned char *) (data_buff + 0) == 0x00 &&
             *(unsigned char *) (data_buff + 1) == 0x00 &&
             *(unsigned char *) (data_buff + 2) == 0x00 &&
             *(unsigned char *) (data_buff + 3) == 0x01 &&
             (((*(unsigned char *) (data_buff + 4)) & 0x1F) == 0x07)) ||
            *(unsigned char *) (data_buff + 0) == 0x00 &&
            *(unsigned char *) (data_buff + 1) == 0x00 &&
            *(unsigned char *) (data_buff + 2) == 0x01 &&
            (((*(unsigned char *) (data_buff + 3)) & 0x1F) == 0x07))//SPS
        {
            while (true) {
                if ((*(unsigned char *) (data_buff + sps_len + 0) == 0x00 &&
                     *(unsigned char *) (data_buff + sps_len + 1) == 0x00 &&
                     *(unsigned char *) (data_buff + sps_len + 2) == 0x00 &&
                     *(unsigned char *) (data_buff + sps_len + 3) == 0x01 &&
                     (((*(unsigned char *) (data_buff + sps_len + 4)) & 0x1F) == 0x08)) ||
                    *(unsigned char *) (data_buff + sps_len + 0) == 0x00 &&
                    *(unsigned char *) (data_buff + sps_len + 1) == 0x00 &&
                    *(unsigned char *) (data_buff + sps_len + 2) == 0x01 &&
                    (((*(unsigned char *) (data_buff + sps_len + 3)) & 0x1F) == 0x08))//PPS
                {
                    memcpy(sps_nalu, data_buff, sps_len);
                    data_buff = data_buff + sps_len;
                    while (true) {
                        if ((*(unsigned char *) (data_buff + pps_len + 0) == 0x00 &&
                             *(unsigned char *) (data_buff + pps_len + 1) == 0x00 &&
                             *(unsigned char *) (data_buff + pps_len + 2) == 0x00 &&
                             *(unsigned char *) (data_buff + pps_len + 3) == 0x01 &&
                             (((*(unsigned char *) (data_buff + pps_len + 4)) & 0x1F) == 0x05)) ||
                            *(unsigned char *) (data_buff + pps_len + 0) == 0x00 &&
                            *(unsigned char *) (data_buff + pps_len + 1) == 0x00 &&
                            *(unsigned char *) (data_buff + pps_len + 2) == 0x01 &&
                            (((*(unsigned char *) (data_buff + pps_len + 3)) & 0x1F) == 0x05)) {
                            memcpy(pps_nalu, data_buff, pps_len);
                            data_buff = data_buff + pps_len;
                            issync = true;
                            break;
                        }
                        pps_len++;
                    }
                    break;
                }
                sps_len++;
            }
            break;
        }
        if ((*(unsigned char *) (data_buff + 0) == 0x00 &&
             *(unsigned char *) (data_buff + 1) == 0x00 &&
             *(unsigned char *) (data_buff + 2) == 0x00 &&
             *(unsigned char *) (data_buff + 3) == 0x01 &&
             (((*(unsigned char *) (data_buff + 4)) & 0x1F) == 0x01)) ||
            *(unsigned char *) (data_buff + 0) == 0x00 &&
            *(unsigned char *) (data_buff + 1) == 0x00 &&
            *(unsigned char *) (data_buff + 2) == 0x01 &&
            (((*(unsigned char *) (data_buff + 3)) & 0x1F) == 0x01)) {
            issync = false;
            break;
        }
        data_buff++;
    }
    *extradata_size = sps_len + pps_len;
    memcpy(extradata, sps_nalu, sps_len);
    memcpy(extradata + sps_len, pps_nalu, pps_len);
    return issync;
}

inline bool
_get_extradata_h265(unsigned char *data, unsigned int data_len, unsigned char *extradata,
                    unsigned int *extradata_size) {
    unsigned char *data_buff = data;
    unsigned char vps_nalu[1024] = {0};
    unsigned int vps_len = 0;
    unsigned char sps_nalu[1024] = {0};
    unsigned int sps_len = 0;
    unsigned char pps_nalu[1024] = {0};
    unsigned int pps_len = 0;
    bool issync = false;
    while (true) {
        if ((*(unsigned char *) (data_buff + 0) == 0x00 &&
             *(unsigned char *) (data_buff + 1) == 0x00 &&
             *(unsigned char *) (data_buff + 2) == 0x00 &&
             *(unsigned char *) (data_buff + 3) == 0x01 &&
             ((((*(unsigned char *) (data_buff + 4)) & 0x7E) >> 1) == 0x20)) ||
            (*(unsigned char *) (data_buff + 0) == 0x00 &&
             *(unsigned char *) (data_buff + 1) == 0x00 &&
             *(unsigned char *) (data_buff + 2) == 0x01 &&
             ((((*(unsigned char *) (data_buff + 3)) & 0x7E) >> 1) == 0x20)))//VPS 32
        {
            while (true) {
                if ((*(unsigned char *) (data_buff + vps_len + 0) == 0x00 &&
                     *(unsigned char *) (data_buff + vps_len + 1) == 0x00 &&
                     *(unsigned char *) (data_buff + vps_len + 2) == 0x00 &&
                     *(unsigned char *) (data_buff + vps_len + 3) == 0x01 &&
                     ((((*(unsigned char *) (data_buff + vps_len + 4)) & 0x7E) >> 1) == 0x21)) ||
                    (*(unsigned char *) (data_buff + vps_len + 0) == 0x00 &&
                     *(unsigned char *) (data_buff + vps_len + 1) == 0x00 &&
                     *(unsigned char *) (data_buff + vps_len + 2) == 0x01 &&
                     ((((*(unsigned char *) (data_buff + vps_len + 3)) & 0x7E) >> 1) ==
                      0x21)))//SPS 33
                {
                    memcpy(vps_nalu, data_buff, vps_len);
                    data_buff = data_buff + vps_len;
                    while (true) {
                        if ((*(unsigned char *) (data_buff + sps_len + 0) == 0x00 &&
                             *(unsigned char *) (data_buff + sps_len + 1) == 0x00 &&
                             *(unsigned char *) (data_buff + sps_len + 2) == 0x00 &&
                             *(unsigned char *) (data_buff + sps_len + 3) == 0x01 &&
                             ((((*(unsigned char *) (data_buff + sps_len + 4)) & 0x7E) >> 1) ==
                              0x22)) ||
                            (*(unsigned char *) (data_buff + sps_len + 0) == 0x00 &&
                             *(unsigned char *) (data_buff + sps_len + 1) == 0x00 &&
                             *(unsigned char *) (data_buff + sps_len + 2) == 0x01 &&
                             ((((*(unsigned char *) (data_buff + sps_len + 3)) & 0x7E) >> 1) ==
                              0x22)))//PPS 34
                        {
                            memcpy(sps_nalu, data_buff, sps_len);
                            data_buff = data_buff + sps_len;
                            while (true) {
                                if ((*(unsigned char *) (data_buff + pps_len + 0) == 0x00 &&
                                     *(unsigned char *) (data_buff + pps_len + 1) == 0x00 &&
                                     *(unsigned char *) (data_buff + pps_len + 2) == 0x00 &&
                                     *(unsigned char *) (data_buff + pps_len + 3) == 0x01 &&
                                     ((((*(unsigned char *) (data_buff + pps_len + 4)) & 0x7E)
                                             >> 1) == 0x13)) ||
                                    *(unsigned char *) (data_buff + pps_len + 0) == 0x00 &&
                                    *(unsigned char *) (data_buff + pps_len + 1) == 0x00 &&
                                    *(unsigned char *) (data_buff + pps_len + 2) == 0x01 &&
                                    ((((*(unsigned char *) (data_buff + pps_len + 3)) & 0x7E)
                                            >> 1) == 0x13))//IDR 19
                                {
                                    memcpy(pps_nalu, data_buff, pps_len);
                                    data_buff = data_buff + pps_len;
                                    issync = true;
                                    break;
                                }
                                pps_len++;
                            }
                            break;
                        }
                        sps_len++;
                    }
                    break;
                }
                vps_len++;
            }
            break;
        }
        if ((*(unsigned char *) (data_buff + 0) == 0x00 &&
             *(unsigned char *) (data_buff + 1) == 0x00 &&
             *(unsigned char *) (data_buff + 2) == 0x00 &&
             *(unsigned char *) (data_buff + 3) == 0x01 &&
             ((((*(unsigned char *) (data_buff + 4)) & 0x7E) >> 1) == 0x01)) ||
            *(unsigned char *) (data_buff + 0) == 0x00 &&
            *(unsigned char *) (data_buff + 1) == 0x00 &&
            *(unsigned char *) (data_buff + 2) == 0x01 &&
            ((((*(unsigned char *) (data_buff + 3)) & 0x7E) >> 1) == 0x01)) {
            issync = false;
            break;
        }
        data_buff++;
    }
    *extradata_size = vps_len + sps_len + pps_len;
    memcpy(extradata, vps_nalu, vps_len);
    memcpy(extradata + vps_len, sps_nalu, sps_len);
    memcpy(extradata + vps_len + sps_len, pps_nalu, pps_len);
    return issync;
}

inline int _write_video_frame_inner(imiffmpegmp4info *handle,
                                    unsigned char *data,
                                    unsigned int data_len,
                                    unsigned int vtimestamp,
                                    bool iskeyframe) {
    bool key = false;
    int ret = 0;
    AVPacket pkt = {0};
    unsigned char *data_buff_src = (unsigned char *) data;
    if (handle->_video_stream == NULL)
        return -1;
    switch (handle->_video_codec) {
        case video_codec_id_h264:
            key = _get_extradata_h264(data_buff_src, data_len, handle->_video_extradata,
                                      &handle->_video_extradata_size);
            break;
        case video_codec_id_h265:
            key = _get_extradata_h265(data_buff_src, data_len, handle->_video_extradata,
                                      &handle->_video_extradata_size);
            break;
        default:
            return -1;
    }
    if (key == false && handle->_getfirstframe == false)
        return -1;
    if (handle->_getfirstframe == false) {
        handle->_video_stream->codec->extradata = handle->_video_extradata;
        handle->_video_stream->codec->extradata_size = handle->_video_extradata_size;
        ret = avformat_write_header(handle->_format_context, NULL);
        if (ret < 0) {
            avcodec_close(handle->_video_stream->codec);
            handle->_video_stream = NULL;
            LOGE("avformat_write_header error");
            return -1;
        }
        handle->_getfirstframe = true;
    }
    av_init_packet(&pkt);
    if (key == true) {
        pkt.flags |= AV_PKT_FLAG_KEY;
        pkt.data = (uint8_t *) data_buff_src + handle->_video_extradata_size;
        pkt.size = data_len - handle->_video_extradata_size;
    } else {
        pkt.data = (uint8_t *) data_buff_src;
        pkt.size = data_len;
    }
    pkt.pts = av_rescale(handle->_video_frame_index++,
                         handle->_video_stream->time_base.den,
                         handle->_video_stream->codec->time_base.den);
    pkt.stream_index = handle->_video_stream->index;
    pkt.dts = pkt.pts;
    pkt.pos = -1;
    if (vtimestamp == 0) {
        pkt.duration = 90000 / handle->_video_fps;
    } else {
        if (handle->_video_lasttimestamp == 0) {
            pkt.duration = 90000 / handle->_video_fps;
        } else {
            pkt.duration = PTS2TIME_SCALE(vtimestamp, handle->_video_lasttimestamp, 90000);
        }
        handle->_video_lasttimestamp = vtimestamp;
    }
    return av_interleaved_write_frame(handle->_format_context, &pkt);
}

int imimp4_write_video_frame(void *handle,
                             unsigned char *data,
                             unsigned int data_len,
                             unsigned int vtimestamp,
                             bool iskeyframe) {
    return _write_video_frame_inner((imiffmpegmp4info *) handle, data, data_len, vtimestamp,
                                    iskeyframe);
}

inline int _write_audio_frame_inner(imiffmpegmp4info *handle,
                                    unsigned char *data,
                                    unsigned int data_len,
                                    unsigned int atimestamp) {
    AVPacket pkt = {0};
    unsigned char *data_buff_src = (unsigned char *) data;
    if (handle->_audio_stream == NULL)
        return -1;
    if (handle->_getfirstframe == false)
        return -1;
    av_init_packet(&pkt);
    pkt.flags |= AV_PKT_FLAG_KEY;
    switch (handle->_audio_codec) {
        case audio_codec_id_aac: {
            int header_len = AAC_ADTS_HEADER;
            pkt.data = (uint8_t *) data_buff_src + header_len;
            pkt.size = data_len - header_len;
            pkt.duration = (int)((double)1024*(double)(handle->_audio_stream->codec->sample_rate/(double)16000));
        }
            break;
        case audio_codec_id_g711a: {
            pkt.data = (uint8_t *) data_buff_src;
            pkt.size = data_len;
            pkt.duration = data_len;
        }
            break;
        default:
            //Ŀǰ��֧��
            return -1;
    }
    pkt.stream_index = handle->_audio_stream->index;
    pkt.pts = pkt.duration * (handle->_audio_frame_index++);
    pkt.dts = pkt.pts;
    pkt.pos = -1;
    return av_interleaved_write_frame(handle->_format_context, &pkt);
}

int imimp4_write_audio_frame(void *handle,
                             unsigned char *data,
                             unsigned int data_len,
                             unsigned int atimestamp) {
    return _write_audio_frame_inner((imiffmpegmp4info *) handle, data, data_len, atimestamp);
}

int _close_file_for_create_inner(imiffmpegmp4info *handle) {
    if (handle->_getfirstframe == true) {
        int r = av_write_trailer(handle->_format_context);
        if (r != 0) {

        }
    }
    if (handle->_video_stream) {
        avcodec_close(handle->_video_stream->codec);
        handle->_video_stream->codec->extradata = NULL;
        handle->_video_stream->codec->extradata_size = 0;
        handle->_video_stream = NULL;
    }
    if (handle->_audio_stream) {
        avcodec_close(handle->_audio_stream->codec);
        handle->_audio_stream->codec->extradata = NULL;
        handle->_audio_stream->codec->extradata_size = 0;
        handle->_audio_stream = NULL;
    }
    avio_close(handle->_format_context->pb);
    handle->_format_context->pb = NULL;
    avformat_free_context(handle->_format_context);
    handle->_format_context = NULL;
    handle->_audio_frame_index = 0;
    if (handle->_audio_extradata) {
        free(handle->_audio_extradata);
        handle->_audio_extradata = NULL;
    }
    handle->_audio_extradata_size = 0;
    handle->_video_frame_index = 0;
    handle->_video_fps = 0;
    handle->_video_lasttimestamp = 0;
    free(handle->_video_extradata);
    handle->_video_extradata = NULL;
    handle->_video_extradata_size = 0;
    handle->_getfirstframe = false;
    return 0;
}

int imimp4_close_file_for_create(void *handle) {
    int r = _close_file_for_create_inner((imiffmpegmp4info *) handle);
    free((imiffmpegmp4info *) handle);
    return r;
}

inline int _open_file_inner(imiffmpegmp4info *handle,
                            const char *path,
                            container_format_id *container,
                            video_codec_id *video,
                            audio_codec_id *audio,
                            unsigned int *vfps,
                            unsigned int *vwidth,
                            unsigned int *vheight,
                            unsigned int *achannel,
                            unsigned int *asamplerate,
                            unsigned int *abitrate) {
    unsigned int i = 0;
    int ret = avformat_open_input(&handle->_format_context, path, NULL, NULL);
    if (ret != 0) {
        LOGE("avformat_open_input error\n");
        goto error;
    }
    ret = avformat_find_stream_info(handle->_format_context, NULL);
    if (ret < 0) {
        LOGE("avformat_find_stream_info error\n");
        goto error;
    }
    if (strstr((char *) handle->_format_context->iformat->extensions, "mp4") == NULL) {
        LOGE("handle->_format_context->iformat->extensions\n");
        goto error;
    }
    *container = container_format_mp4;
    for (i = 0; i < handle->_format_context->nb_streams; i++) {
        AVStream *stream = handle->_format_context->streams[i];
        switch (stream->codec->codec_type) {
            case AVMEDIA_TYPE_VIDEO: {
                switch (stream->codec->codec_id) {
                    case AV_CODEC_ID_H264: {
                        *video = video_codec_id_h264;
                        handle->_video_bsfc = av_bitstream_filter_init("h264_mp4toannexb");
                        if (handle->_video_bsfc == NULL) {
							LOGE("av_bitstream_filter_init h264_mp4toannexb\n");
                            goto error;
                        }
                    }
                        break;
                    case AV_CODEC_ID_H265: {
                        *video = video_codec_id_h265;
                        handle->_video_bsfc = av_bitstream_filter_init("hevc_mp4toannexb");
                        if (handle->_video_bsfc == NULL) {
                            LOGE("av_bitstream_filter_init hevc_mp4toannexb\n");
                            goto error;
                        }
                    }
                        break;
                    default:
                        goto error;
                }
                *vfps = stream->codec->framerate.num;
                *vwidth = stream->codec->width;
                *vheight = stream->codec->height;
            }
                break;
            case AVMEDIA_TYPE_AUDIO: {
                switch (stream->codec->codec_id) {
                    case AV_CODEC_ID_AAC: {
                        *audio = audio_codec_id_aac;
                    }
                        break;
                    case AV_CODEC_ID_PCM_ALAW:
                        *audio = audio_codec_id_g711a;
                        break;
                    default:
                        goto error;
                }
                *achannel = stream->codec->channels;
                *asamplerate = stream->codec->sample_rate;
                *abitrate = (unsigned int) stream->codec->bit_rate;
            }
                break;
            default:
                continue;
        }
    }
    return 0;

    error:
    if (handle->_video_bsfc) {
        av_bitstream_filter_close(handle->_video_bsfc);
        handle->_video_bsfc = NULL;
    }
    if (handle->_format_context) {
        avformat_close_input(&handle->_format_context);
        handle->_format_context = NULL;
    }
    return -1;
}

int imimp4_open_file(const char *path,
                     container_format_id *container,
                     video_codec_id *video,
                     audio_codec_id *audio,
                     unsigned int *vfps,
                     unsigned int *vwidth,
                     unsigned int *vheight,
                     unsigned int *achannel,
                     unsigned int *asamplerate,
                     unsigned int *abitrate,
					 /*out*/void **handle) {
    int ret = 0;
    imiffmpegmp4info *handle_impl = (imiffmpegmp4info *) malloc(sizeof(imiffmpegmp4info));
    if (handle_impl == NULL) {
        return -1;
    }
    memset(handle_impl, 0, sizeof(imiffmpegmp4info));
    ret = _open_file_inner(handle_impl, path, container, video, audio, vfps, vwidth, vheight,
                           achannel, asamplerate, abitrate);
    if (ret != 0) {
        free(handle_impl);
        *handle = NULL;
        return ret;
    }
    *handle = (void *) handle_impl;
    return ret;
}

int _get_video_frame_inner(imiffmpegmp4info *handle,
                           unsigned char *data,
                           unsigned int *data_len,
                           unsigned int *timestamp,
                           bool *iskeyframe,
                           bool *isaudioframe) {
    int ret = 0;
    AVStream *stream = NULL;
    AVPacket pkt = {0};
    if (handle->_format_context == NULL)
        return -1;
    if (*timestamp != 0) {
        int64_t time = (int64_t)(((double) (*timestamp) / (double) 1000) * AV_TIME_BASE +
                                 (double) handle->_format_context->start_time);
        int r = av_seek_frame(handle->_format_context, -1, time,
                              AVSEEK_FLAG_BACKWARD);
        if (r < 0) {
			LOGE("av_seek_frame error\n");
            return r;
        }
    }
    av_init_packet(&pkt);
    ret = av_read_frame(handle->_format_context, &pkt);
    if (ret < 0) {
        av_free_packet(&pkt);
        return ret;
    }
    stream = handle->_format_context->streams[pkt.stream_index];
    switch (stream->codec->codec_type) {
        case AVMEDIA_TYPE_VIDEO: {
            *isaudioframe = false;
            if (pkt.flags & AV_PKT_FLAG_KEY) {
                *iskeyframe = true;
            } else {
                *iskeyframe = false;
            }
            if (handle->_video_bsfc) {
                unsigned char *poutbuf = NULL;
                int poutbuf_size = 0;
                ret = av_bitstream_filter_filter(handle->_video_bsfc, stream->codec, NULL,
                                                 (uint8_t * *) & poutbuf, &poutbuf_size,
                                                 (uint8_t *) pkt.data, pkt.size,
                                                 pkt.flags & AV_PKT_FLAG_KEY);
                if (ret >= 0) {
                    *data_len = poutbuf_size;
                    memcpy(data, poutbuf, poutbuf_size);
                    if (ret > 0) {
                        av_free(poutbuf);
                    }
                } else {
                    *data_len = pkt.size;
                    memcpy(data, (unsigned char *) pkt.data, pkt.size);
                }
            } else {
                *data_len = pkt.size;
                memcpy(data, (unsigned char *) pkt.data, pkt.size);
            }
        }
            break;
        case AVMEDIA_TYPE_AUDIO: {
            *isaudioframe = true;
            if (stream->codec->codec_id == AV_CODEC_ID_AAC) {
                unsigned char *aac_header = imi_make_aac_header_net(stream->codec->sample_rate,
                                                                    stream->codec->channels,
                                                                    pkt.size + AAC_ADTS_HEADER);
                memcpy(data, aac_header, AAC_ADTS_HEADER);
                free(aac_header);
                *data_len = pkt.size + AAC_ADTS_HEADER;
                memcpy(data + AAC_ADTS_HEADER, (unsigned char *) pkt.data, pkt.size);
            } else {
                *data_len = pkt.size;
                memcpy(data, (unsigned char *) pkt.data, pkt.size);
            }
        }
            break;
        default:
            break;
    }
    *timestamp = (unsigned int) (((double) pkt.pts / (double) stream->time_base.den) * 1000);
    av_free_packet(&pkt);
    return 0;
}

int imimp4_get_video_frame(void *handle,
                           unsigned char *data,
                           unsigned int *data_len,
                           unsigned int *timestamp,
                           bool *iskeyframe,
                           bool *isaudioframe) {
    return _get_video_frame_inner((imiffmpegmp4info *) handle, data, data_len, timestamp,
                                  iskeyframe, isaudioframe);
}

int _close_file_for_open_inner(imiffmpegmp4info *handle) {
    if (handle->_video_bsfc) {
        av_bitstream_filter_close(handle->_video_bsfc);
        handle->_video_bsfc = NULL;
    }
    if (handle->_format_context) {
        avformat_close_input(&handle->_format_context);
        handle->_format_context = NULL;
    }
    return 0;
}

int imimp4_close_file_for_open(void *handle) {
    int r = _close_file_for_open_inner((imiffmpegmp4info *) handle);
    free((imiffmpegmp4info *) handle);
    return r;
}